/*
New Scotland Yard is an online multiplayer adaptation
of the boardgame "Scotland Yard". Copyright (C) 2011
Massey University Software C Group 3
This program is free software: you can redistribute it
and/or modify it under the terms of the GNU General
Public License as published by the Free Software
Foundation, either version 3 of the License, or (at
your option) any later version.
This program is distributed in the hope that it will
be useful, but WITHOUT ANY WARRANTY; without even the
implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General
Public License along with this program. If not, see
<http://www.gnu.org/licenses/>.
*/
package nz.ac.massey.softwarec.group3.game;
import java.io.*;
import java.util.*;
import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import nz.ac.massey.softwarec.group3.game.map.Map;
import nz.ac.massey.softwarec.group3.game.map.MapStation;
import nz.ac.massey.softwarec.group3.reverseAJAX.ReverseAJAXCall;
import tokbox.com.opentok.api.*;
import tokbox.com.opentok.api.constants.RoleConstants;
import tokbox.com.opentok.exception.OpenTokException;
/**
* Game - Class for objects that describe a game, and performs all the game operations.
* @version 1.0 Release
* @since 1.0
* @authors Natalie Eustace | Wanting Huang | Paul Smith | Craig Spence
*/
public class Game implements GameInterface {
private final transient String creatorEmail;
private transient String creatorName, openTokSessionId;
private transient List<Player> players = new ArrayList<Player>();
private transient Map map;
private transient MrX mrX;
private final transient List<Move> mrXMoves = new ArrayList<Move>();
private transient boolean hasMrX = false, playing = false, finished = false, doubleMove = false;
private final transient int gameID;
private transient int whosTurn;
/**
* Game constructor to make a new game object.
* @param String creatorEmail - The name of the user creating the game.
* @param int gameID - the ID number of this game.
*/
public Game(final String creatorEmail, final int gameID) {
this.creatorEmail = creatorEmail;
this.gameID = gameID;
Player player;
player = decidePlayerType();
player.setPlayerEmail(creatorEmail);
players.add(player);
}
/**
* Getter for creatorEmail.
* @return String creatorEmail - The name of the user who created the game.
*/
@Override
public String getCreatorEmail() {
return creatorEmail;
}
/**
* Getter for Game creator's name
* @return String creatorName - The full name of the game's creator.
*/
public String getCreatorName() {
return creatorName;
}
/**
* Setter for game creator's name
* @param creatorName
*/
public void setCreatorName(final String creatorName) {
this.creatorName = creatorName;
}
/**
* Getter for game map
* @return map
*/
@Override
public Map getMap() {
return map;
}
/**
* Setter for map
* @param map
*/
@Override
public void setMap(final Map map) {
this.map = map;
}
/**
* Getter for game's id
* @return gameID
*/
@Override
public int getGameID() {
return gameID;
}
/**
* Getter for which player for the current turn
* @return whosTurn
*/
@Override
public int getWhosTurn() {
return whosTurn;
}
/**
* Getter for Mr X's moves
* @return mrXMoves
*/
public List<Move> getMrXMoves() {
return mrXMoves;
}
/**
* Getter for the Player objects.
* @return ArrayList<Player> players - The ArrayList of players who are associated with the game.
*/
@Override
public List<Player> getPlayers() {
return players;
}
/**
* Method to check if playing
* @return true if yes else false
*/
@Override
public boolean isPlaying() {
return this.playing;
}
/**
* Setter to check if playing
* @param playing
*/
@Override
public void setPlaying(final boolean playing) {
this.playing = playing;
}
/**
* Setter to set which player is next to take a turn
* @param playerIndex
*/
@Override
public void setWhosTurn(final int playerIndex) {
this.whosTurn = playerIndex;
}
/**
* Setter for video chat id
*/
public void setTokSessionId() {
final OpenTokSDK sdk = new OpenTokSDK(API_Config.API_KEY,API_Config.API_SECRET);
try {
final String newSessionId = sdk.create_session().session_id;
this.openTokSessionId = newSessionId;
} catch (OpenTokException ex) {
System.err.print(ex);
}
}
//********************************************************//
//Setting up the game to handle video and voice chat
//using the OpenTok library.
/**
* Getter for video chat id
* @return
*/
public String getOpenTokSessionId() {
return openTokSessionId;
}
/**
* Getter for video chat token
* @return
*/
public String getOpenTokIdToken() {
final OpenTokSDK sdk = new OpenTokSDK(API_Config.API_KEY, API_Config.API_SECRET);
try {
return sdk.generate_token(openTokSessionId, RoleConstants.PUBLISHER);
} catch (OpenTokException ex) {
System.err.print(ex);
}
return null;
}
/**
* Getter for video chat API key
* @return
*/
public int getOpenTokAPIKey() {
return API_Config.API_KEY;
}
//********************************************************//
/**
* Getter to see if game is finished.
* @return
*/
public boolean isFinished() {
return finished;
}
/**
* Getter to get a particular player
* @param email
* @return player
*/
@Override
public Player getPlayer(final String email) {
for (Player player : players) {
if (player.getPlayerEmail().equals(email)) {
return player;
}
}
return null;
}
/**
* A method to check if a given user is currently in the game.
* @param String userName - The name of the user who we are checking.
* @return boolean - Whether or not a user is in this game.
*/
@Override
public boolean checkIfUserIsInGameAlready(final String userEmail) {
final List<String> currentEmails = getCurrentPlayerNames();
for (String email : currentEmails) {
if (email.equals(userEmail)) {
//TODO ALREADY IN GAME
return true;
}
}
return false;
}
/**
* Getter for the names of all the players.
* @return ArrayList<String> currentPlayers - The ArrayList of the names of this games players.
*/
private List<String> getCurrentPlayerNames() {
final ArrayList<String> currentPlayers = new ArrayList<String>();
for (Player player : players) {
currentPlayers.add(player.getPlayerEmail());
}
return currentPlayers;
}
/**
* Method that checks if a user is in a game, and if not, creates a new
* Player and adds it to the game.
* @param String userName - The username of the user to be added to the game.
*/
@Override
public void addUserToGame(final String email) {
if (!checkIfUserIsInGameAlready(email)) {
final Player player = decidePlayerType();
if (player == null) {
gameIsFull();
}
else {
player.setPlayerEmail(email);
players.add(player);
}
}
}
/**
* method to remove a particular user from the game
* @param email
* @return true if player removed, false otherwise
*/
@Override
public boolean removeUserFromGame(final String email) {
if (checkIfUserIsInGameAlready(email)) {
for (Player player : players) {
if (player.getPlayerEmail().equals(email)) {
players.remove(player);
return true;
}
}
}
return false;
}
/**
* Method that determines what sort of Player object will be created,
* with type determined randomly (or if there is not a Mr. X when the last
* player is added, the last Player is definitely a Mr. X).
* @return Player - the newly created Player object.
*/
private Player decidePlayerType() {
if (players.size() < 6) {
final double random = Math.random() * 6;
if (!hasMrX && (random > 5 || players.size() == 5)) {
hasMrX = true;
mrX = new MrX();
return mrX;
}
}
return new Detective();
}
/**
* Placeholder to check if game is full
*/
private void gameIsFull() {
throw new UnsupportedOperationException("Not yet implemented");
}
/**
* Method to find out who is Mr X
* @return mrX
*/
public MrX getMrX() {
return mrX;
}
/**
* Method to start the game
*/
@Override
public void startGame() {
assignMrX();
setMrXToFirst();
assignExtraDetectives();
assignStartPositions();
handOutMrXTokens();
setWhosTurn(0);
}
/**
* Method to randomly assign Mr X to a player
*/
private void assignMrX() {
if (!hasMrX) {
final Random random = new Random();
final int nextRandom = random.nextInt(players.size());
mrX = new MrX();
mrX.setPlayerEmail(players.get(nextRandom).getPlayerEmail());
players.set(nextRandom, mrX);
}
}
/**
* Method to ensure MrX is first player to move
*/
private void setMrXToFirst() {
Collections.sort(players);
}
/**
* Method to give players control of more detectives
* if number of players is between and including 3 and 5
*/
private void assignExtraDetectives() {
int detectivePlayerIndex = 1;
int numberOfDetectivePlayers = players.size() - 1;
while (players.size() < 6) {
Detective detective = new Detective();
detective.setPlayerEmail(players.get(detectivePlayerIndex).getPlayerEmail());
players.add(detective);
if (detectivePlayerIndex < numberOfDetectivePlayers) {
detectivePlayerIndex++;
}
else {
detectivePlayerIndex = 1;
}
}
List<String> playerEmails = new ArrayList<String>();
for (Player player : players){
if (!playerEmails.contains(player.getPlayerEmail())) {
playerEmails.add(player.getPlayerEmail());
}
}
List<Player> orderedListOfPlayers = new ArrayList<Player>();
for (String email: playerEmails) {
List<Player> playersForThisEmail = new ArrayList<Player>();
for (Player player: players) {
if (player.getPlayerEmail().equals(email)) {
playersForThisEmail.add(player);
}
}
orderedListOfPlayers.addAll(playersForThisEmail);
}
players = orderedListOfPlayers;
}
/**
* Method to randomly assign players starting positions
*/
private void assignStartPositions() {
final List<Integer> startPositions = new ArrayList<Integer>();
final Random random = new Random();
while (startPositions.size() < 6) {
final int nextRandom = random.nextInt(map.getNumberOfMapStations());
if (!startPositions.contains(nextRandom)) {
startPositions.add(nextRandom);
}
}
for (int i = 0; i < players.size(); i++) {
players.get(i).setCurrentLocation(map.getMapStation(startPositions.get(i)));
}
}
/**
* Method to set number of jetpack(mrX black) tickets for MrX
*/
private void handOutMrXTokens() {
mrX.giveMrXTokens(players.size() - 1);
}
/**
* Method to cause a player to take a turn
* @param value
* @throws ParserConfigurationException
* @throws SAXException
* @throws IOException
*/
public void playerTakesTurn(final String value) throws ParserConfigurationException, SAXException, IOException{
final Move move = createMove(value);
final MapStation mapNode = map.getMapStation(move.getDestinationNode());
if (!mrXLose(move)) {
if ("TAXI".equals(move.getTicketType())) {
players.get(whosTurn).useTaxiToken();
players.get(whosTurn).setCurrentLocation(mapNode);
if (!players.get(whosTurn).isMrX()) {
mrX.giveTravelToken(0);
}
} else if ("BUS".equals(move.getTicketType())) {
players.get(whosTurn).useBusToken();
players.get(whosTurn).setCurrentLocation(mapNode);
if (!players.get(whosTurn).isMrX()) {
mrX.giveTravelToken(1);
}
} else if ("UNDERGROUND".equals(move.getTicketType())) {
players.get(whosTurn).useUndergroundToken();
players.get(whosTurn).setCurrentLocation(mapNode);
if (!players.get(whosTurn).isMrX()) {
mrX.giveTravelToken(2);
}
} else if ("MRX".equals(move.getTicketType())) {
mrX.useMrXToken();
mrX.setCurrentLocation(mapNode);
}
if (players.get(whosTurn).isMrX()) {
mrX.move();
mrXMoves.add(move);
if (mrXMoves.isEmpty() || mrXMoves.size() == 5 || mrXMoves.size() == 10 || mrXMoves.size() == 15 || mrXMoves.size() == 21) {
final ReverseAJAXCall call = new ReverseAJAXCall();
call.createFilteredFunctionCall("/Software_Engineering_C/Resources/JavaServerPages/ingame.jsp", "Game", creatorEmail, "slideOutAlert", "Mr. X will show himself in 3 turns!");
}
if (mrXMoves.size() == 1 || mrXMoves.size() == 6 || mrXMoves.size() == 11 || mrXMoves.size() == 16 || mrXMoves.size() == 22) {
final ReverseAJAXCall call = new ReverseAJAXCall();
call.createFilteredFunctionCall("/Software_Engineering_C/Resources/JavaServerPages/ingame.jsp", "Game", creatorEmail, "slideOutAlert", "Mr. X will show himself in 2 turns!");
}
if (mrXMoves.size() == 2 || mrXMoves.size() == 7 || mrXMoves.size() == 12 || mrXMoves.size() == 17 || mrXMoves.size() == 23) {
final ReverseAJAXCall call = new ReverseAJAXCall();
call.createFilteredFunctionCall("/Software_Engineering_C/Resources/JavaServerPages/ingame.jsp", "Game", creatorEmail, "slideOutAlert", "Mr. X will show himself in 1 turn!");
}
if (mrXMoves.size() == 3 || mrXMoves.size() == 8 || mrXMoves.size() == 13 || mrXMoves.size() == 18 || mrXMoves.size() == 24) {
final ReverseAJAXCall call = new ReverseAJAXCall();
call.createFilteredFunctionCall("/Software_Engineering_C/Resources/JavaServerPages/ingame.jsp", "Game", creatorEmail, "slideOutAlert", "Mr. X has been spotted!");
}
if (mrXMoves.size() == 4 || mrXMoves.size() == 9 || mrXMoves.size() == 14 || mrXMoves.size() == 20) {
final ReverseAJAXCall call = new ReverseAJAXCall();
call.createFilteredFunctionCall("/Software_Engineering_C/Resources/JavaServerPages/ingame.jsp", "Game", creatorEmail, "slideOutAlert", "Mr. X will show himself in 4 turns!");
}
if (mrXMoves.size() == 19) {
final ReverseAJAXCall call = new ReverseAJAXCall();
call.createFilteredFunctionCall("/Software_Engineering_C/Resources/JavaServerPages/ingame.jsp", "Game", creatorEmail, "slideOutAlert", "Mr. X will show himself in 5 turns!");
}
}
determineWhosTurnItIsNext();
}
}
/**
* Method to use to detect if MrX has lost
* @param move
* @return
*/
private boolean mrXLose(final Move move) {
if (!players.get(whosTurn).isMrX()) {
if (map.getMapStation(move.getDestinationNode()) == mrX.getCurrentLocation()) {
broadcastGameEnded("players");
return true;
}
}
if (!getMrX().getCanMove()) {
return true;
}
return false;
}
/**
* Method to check if players have lost
*/
public void checkPlayersLose() {
boolean playersLose = true;
for (Player player : players) {
if (!player.isMrX()) {
if (player.getCanMove()) {
playersLose = false;
}
}
}
if (playersLose) {
broadcastGameEnded("mrX");
}
}
/**
* Method to determine whos turn it is for the next move
*/
public void determineWhosTurnItIsNext() {
if (!doubleMove) {
int nextTurn = whosTurn;
if (nextTurn == players.size() - 1) {
nextTurn = -1;
}
for (int i = nextTurn + 1; i < players.size(); i++) {
if (players.get(i).getInGame()) {
whosTurn = i;
i = players.size();
}
}
tellGameToUpdate(null);
}
else {
mrX.useDoubleMoveToken();
doubleMove = false;
tellGameToUpdate("DOUBLE");
}
}
/**
* Method to use reverse ajax to update
* @param updateParams
*/
private void tellGameToUpdate(final String updateParams) {
final ReverseAJAXCall call = new ReverseAJAXCall();
call.createFilteredFunctionCall("/Software_Engineering_C/Resources/JavaServerPages/ingame.jsp", "Game", creatorEmail, "updateGame", updateParams);
}
/**
* Method to broadcast to all players that the game has ended
* @param winner
*/
private void broadcastGameEnded(final String winner) {
finished = true;
final ReverseAJAXCall call = new ReverseAJAXCall();
call.createFilteredFunctionCall("/Software_Engineering_C/Resources/JavaServerPages/ingame.jsp", "Game", creatorEmail, "endGame", winner);
}
/**
* Method to create a move
* @param XMLstring
* @return
* @throws ParserConfigurationException
* @throws SAXException
* @throws IOException
*/
private Move createMove(final String XMLstring) throws ParserConfigurationException, SAXException, IOException{
String moveType = "";
int originNode = 0;
int destinationNode = 0;
final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
final DocumentBuilder documentBuilder = dbf.newDocumentBuilder();
final InputSource inputSource = new InputSource();
inputSource.setCharacterStream(new StringReader(XMLstring));
final Document document = documentBuilder.parse(inputSource);
//Get move type
final NodeList XMLmoveTypeList = document.getElementsByTagName("movetype");
final Element moveTypeElement = (Element) XMLmoveTypeList.item(0);
moveType = moveTypeElement.getAttribute("type");
//Get origin Node
final NodeList XMLoriginList = document.getElementsByTagName("origin");
final Element originElement = (Element) XMLoriginList.item(0);
originNode = Integer.parseInt(originElement.getAttribute("stationid"));
//Get destination Node
final NodeList XMLdestinationList = document.getElementsByTagName("destination");
final Element destinationElement = (Element) XMLdestinationList.item(0);
destinationNode = Integer.parseInt(destinationElement.getAttribute("stationid"));
return new Move(moveType, originNode, destinationNode);
}
/**
* Method to tell the lobby that there is a game
*/
public void tellLobby() {
final ReverseAJAXCall call = new ReverseAJAXCall();
call.createFunctionCall("/Software_Engineering_C/Resources/JavaServerPages/lobby.jsp", "addGame", null);
}
/**
* Method for double move
*/
public void startDoubleTurn() {
doubleMove = true;
}
/**
* Method to end double move
*/
public void cancelDoubleTurn() {
doubleMove = false;
}
/**
* Method to handle if a player has no tickets to
* move off current station
*/
public void strandedPlayer() {
determineWhosTurnItIsNext();
}
/**
* Method to check if game has ended
*/
public void checkEnd() {
if (!players.get(0).getInGame()) {
broadcastGameEnded("players");
}
if (players.get(0).getInGame() && !players.get(1).getInGame() && !players.get(2).getInGame() && !players.get(3).getInGame() && !players.get(4).getInGame() && !players.get(5).getInGame()) {
broadcastGameEnded("mrX");
}
}
}